import cv2
import math
from matplotlib import pyplot as plt
import numpy as np
from skimage.draw import line
import time
import pyzbar.pyzbar as pyzbar



def show(img, code=cv2.COLOR_BGR2RGB):
    # cv_rgb = cv2.cvtColor(img, code)
    cv_rgb = img
    fig, ax = plt.subplots(figsize=(16, 10))
    ax.imshow(cv_rgb)
    fig.show()

def reshape_image(image):
    '''归一化图片尺寸：短边400，长边不超过800，短边400，长边超过800以长边800为主'''
    width,height=image.shape[1],image.shape[0]
    min_len=width
    scale=width*1.0/400
    new_width=400
    new_height=int(height/scale)
    if new_height>800:
        new_height=800
        scale=height*1.0/800
        new_width=int(width/scale)
    out=cv2.resize(image,(new_width,new_height))
    return out

# 图片预处理
def preProcess(image):
    img = image.copy()
    #归一化处理
    # img = reshape_image(img)

    #灰度化
    img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    #中值滤波
    # img = cv2.medianBlur(img, 3)
    # 直方图均衡 好像不用也没事
    # img = cv2.equalizeHist(img)
    #大津法二值化
    retval, img = cv2.threshold(img, 0, 255, cv2.THRESH_OTSU + cv2.THRESH_BINARY)
    # Canny算子边缘检测
    # img = cv2.Canny(img, 100, 200)

    return img

def isValidCon(contours, i):
    child = hierarchy[i][2]
    nextChild = hierarchy[child][2]
    if (child == -1 or nextChild == -1):
        return False
    parentArea = cv2.contourArea(contours[i])
    childArea = cv2.contourArea(contours[child])
    nextChildArea = cv2.contourArea(contours[nextChild])
    if childArea == 0 or nextChildArea == 0 :
        return False
    th = 1
    ratio1 = parentArea / childArea * 1.0 - 49.0 / 25
    ratio2 = childArea / nextChildArea * 1.0 - 25.0 / 9
    return ratio1 < th and ratio2 < th

# 求距离
def cv_distance(P, Q):
    return int(math.sqrt(pow((P[0] - Q[0]), 2) + pow((P[1] - Q[1]),2)))


#检查2个方格是否是同一个二维码内的方格
def check(a, b):
    # 存储不同box之间最短的两条直线的坐标
    discrete_line = []
    # 存储 ab 数组里最短的两点的组合
    s1_ab = ()
    s2_ab = ()
    # 存储 ab 数组里最短的两点的距离，用于比较
    s1 = np.iinfo('i').max
    s2 = s1
    for ai in a:
        for bi in b:
            d = cv_distance(ai, bi)
            if d < s2:
                if d < s1:
                    s1_ab, s2_ab = (ai, bi), s1_ab
                    s1, s2 = d, s1
                else:
                    s2_ab = (ai, bi)
                    s2 = d
    a1, a2 = s1_ab[0], s2_ab[0]
    b1, b2 = s1_ab[1], s2_ab[1]
    # 缩短2个端点 防止太靠边影响检测效果

    a1 = (a1[0] + (a2[0] - a1[0]) * 1 // 14, a1[1] + (a2[1] - a1[1]) * 1 // 14)
    b1 = (b1[0] + (b2[0] - b1[0]) * 1 // 14, b1[1] + (b2[1] - b1[1]) * 1 // 14)
    a2 = (a2[0] + (a1[0] - a2[0]) * 1 // 14, a2[1] + (a1[1] - a2[1]) * 1 // 14)
    b2 = (b2[0] + (b1[0] - b2[0]) * 1 // 14, b2[1] + (b1[1] - b2[1]) * 1 // 14)
    # len1 = abs(a1 - b1)
    # len2 = abs(a2 - b2)


    # 将最短的两个线画出来
    draw_img = img.copy()
    cv2.line(draw_img, a1, b1, (0,0,255), 5)
    cv2.line(draw_img, a2, b2, (0,0,255), 5)
    #1 show(draw_img)
    # 获取每条线上点的坐标
    discrete_line.append(list(zip(*line(*a1, *b1))))
    discrete_line.append(list(zip(*line(*a2, *b2))))

    color_arr = []

    for dline in discrete_line :
        arr = []
        for i in dline :
            arr.append(bi_img[i[1], i[0]])
        color_arr.append(arr)

    for c in color_arr:
        if isTimingPattern(c) :
            return True

    return False




def isTimingPattern(line):
    # 除去开头结尾的白色像素点
    while line[0] != 0:
        line = line[1:]
    while line[-1] != 0:
        line = line[:-1]
    # 计数连续的黑白像素点
    c = []
    # 阈值的设置 防止边缘的数据对方差造成影响
    COUNT_THROULD = 3
    count = 1
    l = line[0]
    for p in line[1:]:
        if p == l:
            count = count + 1
        else:
            if count > COUNT_THROULD :
                c.append(count)
            count = 1
        l = p
    if count > COUNT_THROULD:
        c.append(count)
    # 如果黑白间隔太少，直接排除
    if len(c) < 5:
        return False
    # 计算方差，根据离散程度判断是否是 Timing Pattern
    threshold = 5
    return np.var(c) < threshold


camera = cv2.VideoCapture(0)
while True:
    ret, frame = camera.read()
    cv2.imshow('frame', frame)
    if cv2.waitKey(1) == ord('q'):
        break

    # img = cv2.imread("1.jpg")
    img = frame.copy()
    start = time.time()
    bi_img = preProcess(img)
    cv2.imshow("bi", bi_img)
    # show(bi_img)



    img_fc, contours, hierarchy = cv2.findContours(bi_img, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

    # 找到嵌套层数大于5的定位标志 数组下标分别为后一个轮廓、前一个轮廓、子轮廓和父轮廓
    # 找有3层嵌套的父轮廓比较靠谱
    # 使用二值化后的图片似乎比边缘检测的图片好一些
    hierarchy = hierarchy[0]
    found = []
    for i in range(len(contours)):
        k = i
        c = 0
        while hierarchy[k][2] != -1:
            k = hierarchy[k][2]
            c = c + 1
        if c > 1 and c < 5:
            if (isValidCon(contours, i)):
                found.append(i)
                #found.add(int(i))
                """
                img_dc = img.copy()
                cv2.drawContours(img_dc, contours, i, (0, 255, 0), 3)
                """

                # print("%d :"%(i), "的最里层轮廓是", "%s :"%(k), hierarchy[k])
                # show(img_dc)
        elif c >= 5:
            break

    """
    for i in found:
        img_dc = img.copy()
        cv2.drawContours(img_dc, contours, i, (0, 255, 0), 3)
        #1 cv2.imshow("Cont"+str(i),img_dc)
    """




    """
    for i in found:
        rect = cv2.minAreaRect(contours[i])
        box = cv2.boxPoints(rect)
        box = np.int0(box)
        #1 cv2.drawContours(draw_img,[box], 0, (0,0,255), 2)
    """



    end = time.time()
    print("程序运行时间为%.3f秒"%(end-start))

    boxes = []
    for i in found:
        rect = cv2.minAreaRect(contours[i])
        box = cv2.boxPoints(rect)
        box = np.int0(box)
        box = tuple(map(tuple, box))
        boxes.append(box)


    end = time.time()
    print("程序运行时间为%.3f秒"%(end-start))

    valid = set()
    if(len(boxes) > 3):
        for i in range(len(boxes)):
            for j in range(i + 1, len(boxes)):
                if check(boxes[i], boxes[j]):
                    valid.add(i)
                    valid.add(j)
    else:
        for i in range(len(boxes)):
            valid.add(i)
    print (valid)

    if (len(valid) < 3) :
        print("没有检测到有效二维码")
    else :
        contour_all = np.array(contours[found[valid.pop()]])
        # contour_all = []
        end = time.time()
        print("未拼接运行时间为%.3f秒" % (end - start))
        while len(valid) > 0:
            c = found[valid.pop()]
            # 拼接多维数组 结果为一维数组
            contour_all = np.append(contour_all, contours[c])
            end = time.time()
            print("拼接运行时间为%.3f秒" % (end - start))

        # 将一维数组重新转化为多维数组
        contour_all = np.array(contour_all).reshape((contour_all.shape)[0] // 2, 1, 2)
        # rect = cv2.minAreaRect(contour_all[])
        rect = cv2.minAreaRect(contour_all)
        box = cv2.boxPoints(rect)
        box = np.array(box)

    #1    draw_img = img.copy()
        # cv2.drawContours(draw_img, contour_all, -1, (0, 255, 0), 10)
        #1 show(draw_img)

        rect = cv2.minAreaRect(contour_all);
        box = cv2.boxPoints(rect)
        box = np.int0(box)
    #    draw_img = img.copy()
        # cv2.drawContours(draw_img, [box], -1, (0, 255, 0), 3)
        #1 show(draw_img)
        points1 = np.float32(box)

        end = time.time()
        print("程序运行时间为%.3f秒" % (end - start))

#        points2 = np.float32([[0, 300], [0, 0], [300, 0], [300, 300]])
        points2 = np.float32([[300, 300], [0, 300], [0, 0], [300, 0]])
        matrix = cv2.getPerspectiveTransform(points1, points2)
        # 将四个点组成的平面转换成另四个点组成的一个平面
        rows, cols, _ = img.shape
        end = time.time()
        print("未透视变换程序运行时间为%.3f秒" % (end - start))
        output = cv2.warpPerspective(img, matrix, (cols, rows))

        end = time.time()
        print("透视变换后,未裁剪程序运行时间为%.3f秒" % (end - start))
        outImg = output[0:300, 0:300]


        end = time.time()
        print("裁剪后程序运行时间为%.3f秒" % (end - start))

        barcodes = pyzbar.decode(outImg)

        for barcode in barcodes:
            barcodeData = barcode.data.decode("utf-8")
            print(barcodeData)
            outImg = cv2.putText(outImg, barcodeData, (10, 100), cv2.FONT_HERSHEY_PLAIN, 1.5, (0, 0, 255), 2)
            cv2.imshow("out", outImg)
    #    draw_img = img.copy()
    #    cv2.polylines(draw_img, np.int32([box]), True, (0, 0, 255), 3)

        #show(draw_img)

        end = time.time()
        print("程序运行时间为%.3f秒" % (end - start))
        cv2.waitKey(0)

